﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;

namespace DaxEditor
{
    /// <summary>
    /// Format DAX query with www.daxformatter.com
    /// </summary>
    public class DaxFormatterCom
    {
        public enum FormattingCulture { US, EU };
        private static string daxFormatterUrl = "http://www.daxformatter.com/api/daxformatter/daxformat";
        private static string redirectUrl = null;

        /// <summary>
        /// Formats a string by calling www.daxformatter.com.  If call is not succeeded, or did not complete in the specified timeout or returned with an error - original string returned
        /// </summary>
        /// <param name="inputDax">input dax text</param>
        /// <returns>formatted text</returns>
        public static string Format(string inputDax, FormattingCulture culture)
        {
            try
            {
                if (redirectUrl == null)
                {
                    // www.daxformatter.com redirects request to another site.  HttpWebRequest does redirect with GET.  It fails, since the web service works only with POST
                    // The following 2 requests are doing manual POST re-direct
                    var redirectRequest = System.Net.WebRequest.Create(daxFormatterUrl) as HttpWebRequest;
                    redirectRequest.AllowAutoRedirect = false;
                    HttpWebResponse redirectResponse = (HttpWebResponse)redirectRequest.GetResponse();
                    redirectUrl = redirectResponse.Headers["Location"];
                }

                var req = System.Net.WebRequest.Create(redirectUrl) as HttpWebRequest;
                req.ContentType = "application/json; charset=utf-8";
                req.Method = "POST";
                var formRequest = ComposeRequest(inputDax, culture);
                var utf8encoding = new UTF8Encoding(false);
                byte[] bytes = utf8encoding.GetBytes(formRequest);
                req.ContentLength = bytes.Length;
                using (var os = req.GetRequestStream())
                {
                    os.Write(bytes, 0, bytes.Length);
                };
                using (var resp = req.GetResponse())
                {
                    if (resp != null)
                    {
                        System.IO.StreamReader sr = new System.IO.StreamReader(resp.GetResponseStream());
                        string response = sr.ReadToEnd();

                        if (!string.IsNullOrWhiteSpace(response))
                        {
                            var decodedResponse = JsonDecode(response);
                            // " are expected in the beggining and the end
                            if (decodedResponse.StartsWith(@"""") && decodedResponse.EndsWith(@""""))
                            {
                                var decodedQueryText = decodedResponse.Substring(1, decodedResponse.Length - 2);
                                if (!string.IsNullOrWhiteSpace(decodedQueryText))
                                    return decodedQueryText;
                            }
                        }
                    }
                }
            }
            catch (Exception)
            {
                // Swallow the exception, return the input back.
            }
            return inputDax;
        }

        private static string ComposeRequest(string inputDax, FormattingCulture cultutre)
        {
            // Sample input: {Dax:""Evaluate table "", ListSeparator:"","", DecimalSeparator:"".""}";
            var sb = new StringBuilder();
            sb.Append(@"{Dax:""");
            // Do other encodings by JSON standard
            string jsonEncodedInputDax = JsonEncode(inputDax);
            sb.Append(jsonEncodedInputDax);
            sb.Append(@""", ");

            if (cultutre == FormattingCulture.EU)
                sb.Append(@"ListSeparator:"";"", DecimalSeparator:"",""}");
            else
                sb.Append(@"ListSeparator:"","", DecimalSeparator:"".""}");

            return sb.ToString();
        }

        private static string JsonEncode(string stringData)
        {
            var sb = new StringBuilder(stringData.Length);
            foreach (char ch in stringData.ToCharArray())
            {
                switch (ch)
                {
                    case '\t':
                        sb.Append(@"\t");
                        break;
                    case '\"':
                        sb.Append(@"\""");
                        break;
                    case '\\':
                        sb.Append(@"\\");
                        break;
                    case '/':
                        sb.Append(@"\/");
                        break;
                    case '\n':
                    case '\r':
                        // This is NOT how \n\r to be encoded in JSON, but in formatting case this is enough.
                        sb.Append(' ');
                        break;
                    default:
                        sb.Append(ch);
                        break;
                }
            }

            return sb.ToString();
        }

        private static string JsonDecode(string jsonEncodedString)
        {
            var sb = new StringBuilder(jsonEncodedString.Length);
            var chars = jsonEncodedString.ToCharArray();
            for (int i = 0; i < chars.Length; i++)
            {
                var c = chars[i];
                switch (c)
                {
                    case '\\':
                        if (++i == chars.Length)
                            throw new InvalidOperationException("Invalid JSON input");
                        c = chars[i];
                        switch (c)
                        {
                            case '\"':
                                sb.Append('\"');
                                break;
                            case '/':
                                sb.Append('/');
                                break;
                            case '\\':
                                sb.Append('\\');
                                break;
                            case 'r':
                                sb.Append('\r');
                                break;
                            case 'n':
                                sb.Append('\n');
                                break;
                            case 't':
                                sb.Append('\t');
                                break;
                            default:
                                throw new InvalidOperationException("Invalid JSON input");
                        }
                        break;

                    default:
                        sb.Append(c);
                        break;
                }
            }

            return sb.ToString();
        }
    }
}
